package com.yn.common.config.swagger;

import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

@Configuration
@EnableSwagger2WebMvc
@Data
@RequiredArgsConstructor // 避免Field injection is not recommended
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnProperty(
        name = {"config.swagger.enable"}
)
public class Knife4jConfig implements BeanFactoryAware, WebMvcConfigurer {
    private static String BEAN_SWAGGER_PREFIX = "swaggerApi";
    private BeanFactory beanFactory;
    // 常量配置
    @Value("${config.swagger.title}")
    private String title;
    @Value("${config.swagger.version}")
    private String version;
    @Value("${config.swagger.termsOfServiceUrl}")
    private String termsOfServiceUrl;

    @Value("${config.swagger.contact.dept}")
    private String contactDept;
    @Value("${config.swagger.contact.url}")
    private String contactUrl;
    @Value("${config.swagger.contact.email}")
    private String contactEmail;
    private final OpenApiExtensionResolver openApiExtensionResolver;
    private final SwaggerProperties swaggerProperties;

    /**
     * 解决knife4j 中doc.html 404问题
     * https://blog.51cto.com/u_2870645/5304212
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry)  {
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    /**
     * 读取配置文件中swagger相关内容，并注册对应的分组bean
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public List<Docket> createSwaggerBean() {
        ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
        Map<String, SwaggerProperties.DockerInfo> docket = swaggerProperties.getDocket();
        List<Docket> retList = new ArrayList<>();
        for (Map.Entry<String, SwaggerProperties.DockerInfo> entry : docket.entrySet()) {
            SwaggerProperties.DockerInfo info = entry.getValue();
            Docket bean = getDocket(info.getGroup(), info.getDescription(), info.getInclude());
            retList.add(bean);
            configurableBeanFactory.registerSingleton(BEAN_SWAGGER_PREFIX + entry.getKey(), bean);
        }
        return retList;
    }

    /**
     * 获取Docket
     *
     * @param groupName   组名
     * @param description 组对应的描述
     * @param pkgs        组对应的包名List
     * @return
     */
    private Docket getDocket(String groupName, String description, List<String> pkgs) {

        //根据注解确定需要显示的接口内容
        //.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
        // 包选择器
        Predicate<RequestHandler> requestHandler = RequestHandlerSelectors.basePackage(pkgs.get(0));

        int length = pkgs.size();
        if (length > 1) {
            for (int i = 1; i < length; i++) {
                requestHandler = requestHandler.or(RequestHandlerSelectors.basePackage(pkgs.get(i)));
            }
        }
        ApiSelectorBuilder selectorBuilder = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        .title(title)
                        .description(description)
                        .termsOfServiceUrl(termsOfServiceUrl)
                        .contact(new Contact(contactDept, contactUrl, contactEmail))
                        .version(version)
                        .build())
                //分组名称
                .groupName(groupName)
                .select();
        Docket docket = selectorBuilder.apis(requestHandler)
                .paths(PathSelectors.any())
                .build()
                .extensions(openApiExtensionResolver.buildSettingExtensions());
        return docket;
    }

}